#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Linq;
using BitwiseOre.SharedMemory.Core.Primatives;
using NUnit.Framework;
using Moq;

namespace BitwiseOre.Tests.SharedMemory.Primatives
{
    [TestFixture]
    public sealed class WriteBufferFixture : AssertionHelper
    {
        [Test(Description = "Checks that writing a byte delgates to the layout contents indexer.")]
        public void WriteByte_DelegatesToContentsIndexer()
        {
            const byte value = 0x33;
            var contents = new Mock<TestContents>();
            contents.SetupGet(c => c.Capacity).Returns(50);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteByte(value);

            contents.Verify(c => c.IndexerSet(It.IsAny<int>(), It.Is<byte>(b => b == value)),
                            "Writing a byte did not delegate the the layout contents indexer.");
        }

        [Test(Description = "Checks that the first instance of writing a byte writes to offset 0.")]
        public void WriteByte_WritesToOffsetZeroOnInitialCall()
        {
            var contents = new Mock<TestContents>();
            contents.SetupGet(c => c.Capacity).Returns(50);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteByte(0x33);

            contents.Verify(c => c.IndexerSet(It.Is<int>(i => i == 0), It.IsAny<byte>()),
                            "Writing the first byte did not write at index 0");
            
        }

        [Test(Description = "Checks that the second (and later) instance of writing a byte writes to later offsets")]
        public void WriteByte_WritesToSubsequentOffsetOnSubsequentCalls()
        {
            var contents = new Mock<TestContents>();
            contents.SetupGet(c => c.Capacity).Returns(50);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupProperty(l => l.Length);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteByte(0x33);
            buffer.WriteByte(0x33);
            buffer.WriteByte(0x33);

            contents.Verify(c => c.IndexerSet(It.Is<int>(i => i > 0), It.IsAny<byte>()),
                            Times.Exactly(2),
                            "Writing the second and later bytes did not write at later offsets.");
        }

        [Test(Description = "Checks that writing a byte increments the length of the buffer.")]
        public void WriteByte_IncrementsLength()
        {
            var layout = new Mock<IBufferLayout>{DefaultValue = DefaultValue.Mock};
            layout.SetupGet(l => l.Length).Returns(0);
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteByte(0x33);
            
            layout.VerifySet(l => l.Length = 1, "Length not set to 1 on initial writing of a byte.");
        }

        [Test(Description = "Checks that attempting to write a byte does not delegate to the contents " +
                            "indexer if the length of the buffer is at the capacity of the contents.")]
        public void WriteByte_DoesNotDelegateToIndexerIfLengthAtCapactity()
        {
            const int capacity = 5;
            var contents = new Mock<TestContents>();
            contents.SetupGet(c => c.Capacity).Returns(capacity);
            var layout = new Mock<IBufferLayout>();
            layout.SetupGet(l => l.Contents).Returns(contents.Object);
            layout.SetupGet(l => l.Length).Returns(capacity);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteByte(0x33);
            
            contents.Verify(c => c.IndexerSet(It.IsAny<int>(), It.IsAny<byte>()), Times.Never(),
                            "Indexer setter called when buffer length at capacity.");
        }

        [Test(Description = "Checks that writing a byte returns true if the length of the buffer is not " +
                            "at the capacity of the contents.")]
        [Sequential]
        public void WriteByte_ReturnsTrueIfNotAtCapactiy(
            [Values(5,10)]int length, 
            [Values(10,10)]int capacity, 
            [Values(true, false)]bool expectedresult)
        {
            var layout = new Mock<IBufferLayout>{DefaultValue = DefaultValue.Mock};
            layout.SetupGet(l => l.Length).Returns(length);
            layout.SetupGet(l => l.Contents.Capacity).Returns(capacity);

            var buffer = new WriteBuffer(layout.Object);
            var result = buffer.WriteByte(0x33);

            Expect(result, EqualTo(expectedresult), "Unexpected result returned from WriteByte()");
        }

        [Test(Description = "Checks that writing an array segment delegates the write to the contents array")]
        public void WriteArraySegment_DelegatesToContents()
        {
            var array = new ArraySegment<byte>(new byte[] {0x33, 0x44, 0x55});
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteArraySegment(array);

            layout.Verify(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()),
                "WriteArraySegment did not delgate to contents of the buffer.");
        }

        [Test(Description = "Checks that writing an array segment retruns the results of the delgated call.")]
        public void WriteArraySegment_ReturnsResultsOfDelgatedCall()
        {
            const int expectedresult = 5;
            var array = new ArraySegment<byte>(new byte[] {0x33, 0x44, 0x55});
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.Setup(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns(expectedresult);
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            var result = buffer.WriteArraySegment(array);

            Expect(result, EqualTo(expectedresult));
        }

        [Test(Description = "Checks that the first instance of writing an array segment writes to offset 0")]
        public void WriteArraySegment_WritesToOffsetZeroOnInitalCall()
        {
            var array = new ArraySegment<byte>(new byte[] {0x33, 0x44, 0x55});
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.SetupProperty(l => l.Length);
            layout.Setup(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns((int _, ArraySegment<byte> seg) => seg.Count);
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteArraySegment(array);
            
            layout.Verify(l => l.Contents.WriteArraySegment(It.Is<int>(i => i == 0), It.IsAny<ArraySegment<byte>>()),
                          "Initial array segment not written at offset 0.");
        }

        [Test(Description = "Checks that the second instance of writing an array segment writes to an " +
                            "offset based on the count of the bytes writting on the first write.")]
        public void WriteArraySegment_WritesToOffsetBasedOnFirstWriteCountOnSecondWrite()
        {
            var array = (from i in Enumerable.Range(0x33, 20)
                         select (byte) i).ToArray();
            const int count1 = 5;
            const int count2 = 3;
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.SetupProperty(l => l.Length);
            layout.Setup(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns((int _, ArraySegment<byte> seg) => seg.Count);
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteArraySegment(new ArraySegment<byte>(array, 0, count1));
            buffer.WriteArraySegment(new ArraySegment<byte>(array, 0, count2));

            layout.Verify(l => l.Contents.WriteArraySegment(It.Is<int>(i => i == count1),
                                                            It.IsAny<ArraySegment<byte>>()),
                          "Subseqent array segment not written at offset based on count of initial write.");
        }


        [Test(Description = "Writing an array segment increments the length of the buffer by the count " +
                            "written to the contents array.")]
        public void WriteArraySegment_IncrementsLength()
        {
            const int count = 5;
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.Setup(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()))
                .Returns(count);
            layout.SetupGet(l => l.Contents.Capacity).Returns(50);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteArraySegment(new ArraySegment<byte>(new byte[] {0x33, 0x44, 0x55}));

            layout.VerifySet(l => l.Length = count, "Length not set to expected value.");
        }

        [Test(Description = "Attempting to write an array segment when the length of the buffer is at the " +
                            "capacity of the contents does not delegate to the contents")]
        public void WriteArraySegment_DoesNotDelegateToContentsIfLengthAtCapacity()
        {
            const int capacity = 5;
            var layout = new Mock<IBufferLayout> {DefaultValue = DefaultValue.Mock};
            layout.SetupGet(l => l.Length).Returns(capacity);
            layout.SetupGet(l => l.Contents.Capacity).Returns(capacity);

            var buffer = new WriteBuffer(layout.Object);
            buffer.WriteArraySegment(new ArraySegment<byte>(new byte[] {3, 5, 8, 7}));

            layout.Verify(l => l.Contents.WriteArraySegment(It.IsAny<int>(), It.IsAny<ArraySegment<byte>>()),
                          Times.Never(),
                          "Writing array segment delgated to the contents.");
        }


        // ReSharper disable MemberCanBePrivate.Global
        public abstract class TestContents : IByteArray
        {
            public byte this[int index]
            {
                get { throw new NotImplementedException(); }
                set { IndexerSet(index, value); }
            }

            public abstract void IndexerSet(int index, byte value);

            public abstract int Capacity { get; }

            public int Count
            {
                get { throw new NotImplementedException(); }
            }

            public int ReadArraySegment(int index, ArraySegment<byte> destination)
            {
                throw new NotImplementedException();
            }

            public int WriteArraySegment(int offset, ArraySegment<byte> source)
            {
                throw new NotImplementedException();
            }
        }
        // ReSharper restore MemberCanBePrivate.Global
    }
}